#include "Precompiled.h"
#include "GLShader.h"

#include "Platform/OpenGL/GL.h"
#include "Core/OS/FileSystem.h"
#include "Core/OS/FileSystem.h"
#include "Utilities/StringUtilities.h"
#include "Maths/MathsUtilities.h"

#include <spirv_glsl.hpp>

enum root_signature_spaces
{
    PUSH_CONSTANT_REGISTER_SPACE = 0,
    DYNAMIC_OFFSET_SPACE,
    DESCRIPTOR_TABLE_INITIAL_SPACE,
};

int PUSHCONSTANT_BINDING = 64;

namespace Lumos
{
    namespace Graphics
    {
        bool IGNORE_LINES        = false;
        static ShaderType s_Type = ShaderType::UNKNOWN;

        uint32_t GetStrideFromOpenGLFormat(uint32_t format)
        {
            // switch(format)
            //{
            //     //                case VK_FORMAT_R8_SINT:
            //     //                return sizeof(int);
            //     //                case VK_FORMAT_R32_SFLOAT:
            //     //                return sizeof(float);
            //     //                case VK_FORMAT_R32G32_SFLOAT:
            //     //                return sizeof(Vec2);
            //     //                case VK_FORMAT_R32G32B32_SFLOAT:
            //     //                return sizeof(Vec3);
            //     //                case VK_FORMAT_R32G32B32A32_SFLOAT:
            //     //                return sizeof(Vec4);
            // default:
            //     //LERROR("Unsupported Format {0}", format);
            //     return 0;
            // }

            return 0;
        }

        void PushTypeToBuffer(const spirv_cross::SPIRType type, Graphics::BufferLayout& layout)
        {
            switch(type.basetype)
            {
            case spirv_cross::SPIRType::Float:
                switch(type.vecsize)
                {
                case 1:
                    layout.Push<float>("");
                    break;
                case 2:
                    layout.Push<Vec2>("");
                    break;
                case 3:
                    layout.Push<Vec3>("");
                    break;
                case 4:
                    layout.Push<Vec4>("");
                    break;
                }
            case spirv_cross::SPIRType::Double:
                break;
            default:
                break;
            }
        }

        GLShader::GLShader(const std::string& filePath)
        {
            m_Name = StringUtilities::GetFileName(filePath);
            m_Path = StringUtilities::GetFileLocation(filePath);

            m_Source = FileSystem::Get().ReadTextFile(filePath);

            Init();
        }

        GLShader::GLShader(const uint32_t* vertData, uint32_t vertDataSize, const uint32_t* fragData, uint32_t fragDataSize)
        {
            std::map<ShaderType, std::string>* sources = new std::map<ShaderType, std::string>();

            LoadFromData(vertData, vertDataSize, ShaderType::VERTEX, *sources);
            LoadFromData(fragData, fragDataSize, ShaderType::FRAGMENT, *sources);

            for(auto& source : *sources)
            {
                m_ShaderTypes.PushBack(source.first);
            }

            GLShaderErrorInfo error;
            m_Handle = Compile(sources, error);

            if(!m_Handle)
            {
                LERROR("%s - %s", error.message[error.shader].c_str(), m_Name.c_str());
            }
            else
            {
                LINFO("Successfully compiled shader: %s", m_Name.c_str());
            }

            CreateLocations();

            delete sources;
        }

        GLShader::GLShader(const uint32_t* compData, uint32_t compDataSize)
        {
            std::map<ShaderType, std::string>* sources = new std::map<ShaderType, std::string>();

            LoadFromData(compData, compDataSize, ShaderType::COMPUTE, *sources);

            for(auto& source : *sources)
            {
                m_ShaderTypes.PushBack(source.first);
            }

            GLShaderErrorInfo error;
            m_Handle = Compile(sources, error);

            if(!m_Handle)
            {
                LERROR("%s - %s", error.message[error.shader].c_str(), m_Name.c_str());
            }
            else
            {
                LINFO("Successfully compiled shader: %s", m_Name.c_str());
            }

            CreateLocations();

            delete sources;
        }

        GLShader::~GLShader()
        {
            Shutdown();

            for(auto& pc : m_PushConstants)
                delete[] pc.data;

            for(auto& pcb : m_PushConstantsBuffers)
                delete pcb.first;
        }

        void GLShader::Init()
        {
            LUMOS_PROFILE_FUNCTION();
            std::map<ShaderType, std::string>* sources = new std::map<ShaderType, std::string>();
            PreProcess(m_Source, sources);

            for(auto& file : *sources)
            {
                auto fileSize    = FileSystem::GetFileSize(m_Path + file.second); // TODO: once process
                uint32_t* source = reinterpret_cast<uint32_t*>(FileSystem::ReadFile(m_Path + file.second));
                LoadFromData(source, uint32_t(fileSize), file.first, *sources);
            }

            for(auto& source : *sources)
            {
                m_ShaderTypes.PushBack(source.first);
            }

            GLShaderErrorInfo error;
            m_Handle = Compile(sources, error);

            if(!m_Handle)
            {
                LERROR("%s - %s", error.message[error.shader].c_str(), m_Name.c_str());
            }
            else
            {
                LINFO("Successfully compiled shader: %s", m_Name.c_str());
            }

            CreateLocations();

            delete sources;
        }

        void GLShader::Shutdown() const
        {
            LUMOS_PROFILE_FUNCTION();
            GLCall(glDeleteProgram(m_Handle));
        }

        GLuint HashValue(const char* szString)
        {
            LUMOS_PROFILE_FUNCTION();
            const char* c      = szString;
            GLuint dwHashValue = 0x00000000;

            while(*c)
            {
                dwHashValue = (dwHashValue << 5) - dwHashValue + (*c == '/' ? '\\' : *c);
                c++;
            }

            return dwHashValue ? dwHashValue : 0xffffffff;
        }

        void GLShader::SetUniform(ShaderDataType type, uint8_t* data, uint32_t size, uint32_t offset, const std::string& name)
        {
            LUMOS_PROFILE_FUNCTION();

            GLuint hashName = HashValue(name.c_str());

            if(m_UniformLocations.find(hashName) == m_UniformLocations.end())
            {
                GLuint location = glGetUniformLocation(m_Handle, name.c_str());

                if(location != GL_INVALID_INDEX)
                {
                    m_UniformLocations[hashName] = location;
                }
                else
                {
                    LWARN("Invalid uniform location %s", name.c_str());
                }
            }

            auto location = m_UniformLocations[hashName];
            if(location == -1)
            {
                LERROR("Couldnt Find Uniform In Shader: %s", name.c_str());
                return;
            }

            switch(type)
            {
            case ShaderDataType::FLOAT32:
                SetUniform1f(location, *reinterpret_cast<float*>(&data[offset]));
                break;
            case ShaderDataType::INT32:
                SetUniform1i(location, *reinterpret_cast<int32_t*>(&data[offset]));
                break;
            case ShaderDataType::UINT:
                SetUniform1ui(location, *reinterpret_cast<uint32_t*>(&data[offset]));
                break;
            case ShaderDataType::INT:
                SetUniform1i(location, *reinterpret_cast<int*>(&data[offset]));
                break;
            case ShaderDataType::VEC2:
                SetUniform2f(location, *reinterpret_cast<Vec2*>(&data[offset]));
                break;
            case ShaderDataType::VEC3:
                SetUniform3f(location, *reinterpret_cast<Vec3*>(&data[offset]));
                break;
            case ShaderDataType::VEC4:
                SetUniform4f(location, *reinterpret_cast<Vec4*>(&data[offset]));
                break;
            case ShaderDataType::MAT3:
                SetUniformMat3(location, *reinterpret_cast<Mat3*>(&data[offset]));
                break;
            case ShaderDataType::MAT4:
                SetUniformMat4(location, *reinterpret_cast<Mat4*>(&data[offset]));
                break;
            case ShaderDataType::MAT4ARRAY:
                SetUniformMat4Array(location, 1, *reinterpret_cast<Mat4*>(&data[offset]));
                break;
            default:
                ASSERT(false, "Unknown type!");
            }
        }

        void GLShader::BindPushConstants(Graphics::CommandBuffer* commandBuffer, Graphics::Pipeline* pipeline)
        {
            LUMOS_PROFILE_FUNCTION();
            for(int i = 0; i < m_PushConstants.Size(); i++)
            {
                // for(auto& member : pc.m_Members)
                //{
                //     SetUniform(member.type, pc.data, member.size, member.offset, member.fullName);
                // }
                m_PushConstantsBuffers[i].first->SetData(m_PushConstants[i].size, m_PushConstants[i].data);
                // GLuint nameInt         = HashValue(m_PushConstants[i].name.c_str());

                // BindUniformBuffer(m_PushConstantsBuffers[i].first, m_PushConstantsBuffers[i].second, m_PushConstants[i].name);

                const auto& itLocation = m_UniformBlockLocations.find(m_PushConstants[i].name);
                if(itLocation == m_UniformBlockLocations.end())
                    LWARN("Push const %s not found", m_PushConstants[i].name.c_str());
                auto bufferHandle = static_cast<GLUniformBuffer*>(m_PushConstantsBuffers[i].first)->GetHandle();
                {
                    LUMOS_PROFILE_SCOPE("glBindBufferBase");
                    GLCall(glBindBufferBase(GL_UNIFORM_BUFFER, PUSHCONSTANT_BINDING, bufferHandle));
                }

                // if(buffer->GetDynamic())
                {
                    LUMOS_PROFILE_SCOPE("glBindBufferRange");
                    GLCall(glBindBufferRange(GL_UNIFORM_BUFFER, PUSHCONSTANT_BINDING, bufferHandle, 0, m_PushConstants[i].size));
                }
            }
        }

        bool GLShader::CreateLocations()
        {
            LUMOS_PROFILE_FUNCTION();
            for(auto& compiler : m_ShaderCompilers)
            {
                const spirv_cross::ShaderResources shaderResources = compiler->get_shader_resources();

                for(const auto& itUniform : shaderResources.uniform_buffers)
                {
                    if(compiler->get_type(itUniform.type_id).basetype == spirv_cross::SPIRType::Struct)
                    {
                        SetUniformLocation(itUniform.name.c_str());
                    }
                }

                for(const auto& itUniform : shaderResources.push_constant_buffers)
                {
                    if(compiler->get_type(itUniform.type_id).basetype == spirv_cross::SPIRType::Struct)
                    {
                        // auto slot         = descriptor.binding;
                        auto name = compiler->get_name(itUniform.base_type_id);

                        SetUniformLocation(name.c_str(), true);
                    }
                    else
                    {
                        auto name = compiler->get_name(itUniform.base_type_id);
                        SetUniformLocation(name.c_str(), true);
                    }
                }
            }
            return true;
        }

        void GLShader::BindUniformBuffer(GLUniformBuffer* buffer, uint32_t slot, const std::string& name)
        {
            LUMOS_PROFILE_FUNCTION();
            // GLuint nameInt         = HashValue(name.c_str());
            const auto& itLocation = m_UniformBlockLocations.find(name);
            GLCall(glUniformBlockBinding(m_Handle, itLocation->second, slot));
        }

        bool GLShader::SetUniformLocation(const std::string& szName, bool pc)
        {
            LUMOS_PROFILE_FUNCTION();
            // GLuint name = HashValue(szName);

            if(m_UniformBlockLocations.find(szName) == m_UniformBlockLocations.end())
            {
                GLuint location = glGetUniformBlockIndex(m_Handle, szName.c_str());

                if(location != GL_INVALID_INDEX)
                {
                    // Should only be used for push constant uniform buffers
                    m_UniformBlockLocations[szName] = location;
                    glUniformBlockBinding(m_Handle, location, pc ? PUSHCONSTANT_BINDING : location);
                    return true;
                }
            }

            LWARN("Couldnt set uniform location");
            return false;
        }

        void GLShader::PreProcess(const std::string& source, std::map<ShaderType, std::string>* sources)
        {
            LUMOS_PROFILE_FUNCTION();
            s_Type                     = ShaderType::UNKNOWN;
            TDArray<std::string> lines = StringUtilities::GetLines(source);
            ReadShaderFile(lines, sources);
        }

        void GLShader::ReadShaderFile(TDArray<std::string> lines, std::map<ShaderType, std::string>* shaders)
        {
            LUMOS_PROFILE_FUNCTION();
            for(uint32_t i = 0; i < lines.Size(); i++)
            {
                std::string str = std::string(lines[i]);
                str             = StringUtilities::StringReplace(str, '\t');

                if(IGNORE_LINES)
                {
                    if(StringUtilities::StartsWith(str, "#end"))
                    {
                        IGNORE_LINES = false;
                    }
                }
                else if(StringUtilities::StartsWith(str, "#shader"))
                {
                    if(StringUtilities::StringContains(str, "vertex"))
                    {
                        s_Type                                         = ShaderType::VERTEX;
                        std::map<ShaderType, std::string>::iterator it = shaders->begin();
                        shaders->insert(it, std::pair<ShaderType, std::string>(s_Type, ""));
                    }
                    else if(StringUtilities::StringContains(str, "geometry"))
                    {
                        s_Type                                         = ShaderType::GEOMETRY;
                        std::map<ShaderType, std::string>::iterator it = shaders->begin();
                        shaders->insert(it, std::pair<ShaderType, std::string>(s_Type, ""));
                    }
                    else if(StringUtilities::StringContains(str, "fragment"))
                    {
                        s_Type                                         = ShaderType::FRAGMENT;
                        std::map<ShaderType, std::string>::iterator it = shaders->begin();
                        shaders->insert(it, std::pair<ShaderType, std::string>(s_Type, ""));
                    }
                    else if(StringUtilities::StringContains(str, "tess_cont"))
                    {
                        s_Type                                         = ShaderType::TESSELLATION_CONTROL;
                        std::map<ShaderType, std::string>::iterator it = shaders->begin();
                        shaders->insert(it, std::pair<ShaderType, std::string>(s_Type, ""));
                    }
                    else if(StringUtilities::StringContains(str, "tess_eval"))
                    {
                        s_Type                                         = ShaderType::TESSELLATION_EVALUATION;
                        std::map<ShaderType, std::string>::iterator it = shaders->begin();
                        shaders->insert(it, std::pair<ShaderType, std::string>(s_Type, ""));
                    }
                    else if(StringUtilities::StringContains(str, "compute"))
                    {
                        s_Type                                         = ShaderType::COMPUTE;
                        std::map<ShaderType, std::string>::iterator it = shaders->begin();
                        shaders->insert(it, std::pair<ShaderType, std::string>(s_Type, ""));
                    }
                    else if(StringUtilities::StringContains(str, "end"))
                    {
                        s_Type = ShaderType::UNKNOWN;
                    }
                }
                else if(StringUtilities::StartsWith(str, "#include"))
                {
                    std::string rem  = "#include ";
                    std::string file = std::string(str);
                    if(strstr(file.c_str(), rem.c_str()))
                    {
                        std::string::size_type j = file.find(rem);
                        if(j != std::string::npos)
                            file.erase(j, rem.length());
                        file = StringUtilities::StringReplace(file, '\"');
                        LWARN("Including file \'%s\' into shader.", file.c_str());
                        FileSystem::Get().ReadTextFile(file);
                        ReadShaderFile(StringUtilities::GetLines(FileSystem::Get().ReadTextFile(file)), shaders);
                    }
                }
                else if(StringUtilities::StartsWith(str, "#if"))
                {
                    std::string rem = "#if ";
                    std::string def = std::string(str);
                    if(strstr(def.c_str(), rem.c_str()))
                    {
                        std::string::size_type j = def.find(rem);
                        if(j != std::string::npos)
                            def.erase(j, rem.length());
                        def = StringUtilities::StringReplace(def, '\"');

                        if(def == "0")
                        {
                            IGNORE_LINES = true;
                        }
                    }
                }
                else if(s_Type != ShaderType::UNKNOWN)
                {
                    shaders->at(s_Type).append(lines[i]);
                    /// Shaders->at(s_Type).append("\n");
                }
            }
        }

        uint32_t GLShader::Compile(std::map<ShaderType, std::string>* sources, GLShaderErrorInfo& info)
        {
            LUMOS_PROFILE_FUNCTION();
            GLCall(uint32_t program = glCreateProgram());

            TDArray<GLuint> shaders;

            std::string glVersion;

#ifndef LUMOS_PLATFORM_MOBILE
            glVersion = "#version 410 core \n";
#else
            glVersion = "#version 300 es \n precision highp float; \n precision highp int; \n";
#endif

            for(auto source : *sources)
            {
                // source.second.insert(0, glVersion);
                // LINFO(source.second);
                shaders.PushBack(CompileShader(source.first, source.second, program, info));
            }

            for(unsigned int shader : shaders)
                glAttachShader(program, shader);

            GLCall(glLinkProgram(program));

            GLint result;
            GLCall(glGetProgramiv(program, GL_LINK_STATUS, &result));
            if(result == GL_FALSE)
            {
                GLint length;
                GLCall(glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length));
                TDArray<char> error(length);
                GLCall(glGetProgramInfoLog(program, length, &length, error.Data()));
                std::string errorMessage(error.Data(), length);
                int32_t lineNumber = -1;
                sscanf(error.Data(), "%*s %*d:%d", &lineNumber);
                info.shader = 3;
                info.message[info.shader] += "Failed to link shader!\n";
                info.line[info.shader] = 0;
                info.message[info.shader] += errorMessage;

                LERROR(info.message[info.shader].c_str());
                return 0;
            }

            GLCall(glValidateProgram(program));

            for(int z = 0; z < shaders.Size(); z++)
                glDetachShader(program, shaders[z]);

            for(int z = 0; z < shaders.Size(); z++)
                glDeleteShader(shaders[z]);

            return program;
        }

        GLenum TypeToGL(ShaderType type)
        {
            switch(type)
            {
            case ShaderType::VERTEX:
                return GL_VERTEX_SHADER;
            case ShaderType::FRAGMENT:
                return GL_FRAGMENT_SHADER;
#ifndef LUMOS_PLATFORM_MOBILE
            case ShaderType::GEOMETRY:
                return GL_GEOMETRY_SHADER;
            case ShaderType::TESSELLATION_CONTROL:
                return GL_TESS_CONTROL_SHADER;
            case ShaderType::TESSELLATION_EVALUATION:
                return GL_TESS_EVALUATION_SHADER;
            case ShaderType::COMPUTE:
                return GL_COMPUTE_SHADER;
#endif
            default:
                LERROR("Unsupported Shader Type");
                return GL_VERTEX_SHADER;
            }
        }

        std::string TypeToString(ShaderType type)
        {
            switch(type)
            {
            case ShaderType::VERTEX:
                return "GL_VERTEX_SHADER";
            case ShaderType::FRAGMENT:
                return "GL_FRAGMENT_SHADER";
            case ShaderType::GEOMETRY:
                return "GL_GEOMETRY_SHADER";
            case ShaderType::TESSELLATION_CONTROL:
                return "GL_TESS_CONTROL_SHADER";
            case ShaderType::TESSELLATION_EVALUATION:
                return "GL_TESS_EVALUATION_SHADER";
            case ShaderType::COMPUTE:
                return "GL_COMPUTE_SHADER";
            case ShaderType::UNKNOWN:
                return "UNKOWN_SHADER";
            }
            return "N/A";
        }

        uint32_t GLShader::CompileShader(ShaderType type, std::string source, uint32_t program, GLShaderErrorInfo& info)
        {
            LUMOS_PROFILE_FUNCTION();
            const char* cstr = source.c_str();

            GLCall(GLuint shader = glCreateShader(TypeToGL(type)));
            GLCall(glShaderSource(shader, 1, &cstr, NULL));
            GLCall(glCompileShader(shader));

            GLint result;
            GLCall(glGetShaderiv(shader, GL_COMPILE_STATUS, &result));
            if(result == GL_FALSE)
            {
                GLint length;
                GLCall(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length));
                TDArray<char> error(length);
                GLCall(glGetShaderInfoLog(shader, length, &length, error.Data()));
                std::string errorMessage(error.Data(), length);
                int32_t lineNumber;
                sscanf(error.Data(), "%*s %*d:%d", &lineNumber);
                info.shader = static_cast<uint32_t>(type);
                info.message[info.shader] += "Failed to compile " + TypeToString(type) + " shader!\n";

                info.line[info.shader] = lineNumber;
                info.message[info.shader] += errorMessage;
                GLCall(glDeleteShader(shader));

                LERROR(info.message[info.shader].c_str());
                return 0;
            }
            return shader;
        }

        void GLShader::Bind() const
        {
            if(s_CurrentlyBound != this)
            {
                GLCall(glUseProgram(m_Handle));
                s_CurrentlyBound = this;
            }
        }

        void GLShader::Unbind() const
        {
            GLCall(glUseProgram(0));
            s_CurrentlyBound = nullptr;
        }

        bool GLShader::IsTypeStringResource(const std::string& type)
        {
            if(type == "sampler2D")
                return true;
            if(type == "samplerCube")
                return true;
            if(type == "sampler2DShadow")
                return true;
            if(type == "sampler2DArrayShadow")
                return true;
            return false;
        }

        GLint GLShader::GetUniformLocation(const std::string& name)
        {
            LUMOS_PROFILE_FUNCTION();
            GLuint hashName = HashValue(name.c_str());

            if(m_UniformLocations.find(hashName) == m_UniformLocations.end())
            {
                GLuint location = glGetUniformLocation(m_Handle, name.c_str());

                if(location != GL_INVALID_INDEX)
                {
                    m_UniformLocations[hashName] = location;
                }
                else
                {
                    LWARN("Invalid uniform location %s", name.c_str());
                }
            }

            auto location = m_UniformLocations[hashName];
            if(location == -1)
            {
                LERROR("Couldnt Find Uniform In Shader: %s", name.c_str());
                return location;
            }

            return location;
        }

        void GLShader::SetUniform1f(const std::string& name, float value)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform1f(GetUniformLocation(name), value);
        }

        void GLShader::SetUniform1fv(const std::string& name, float* value, int32_t count)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform1fv(GetUniformLocation(name), value, count);
        }

        void GLShader::SetUniform1i(const std::string& name, int32_t value)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform1i(GetUniformLocation(name), value);
        }

        void GLShader::SetUniform1ui(const std::string& name, uint32_t value)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform1ui(GetUniformLocation(name), value);
        }

        void GLShader::SetUniform1iv(const std::string& name, int32_t* value, int32_t count)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform1iv(GetUniformLocation(name), value, count);
        }

        void GLShader::SetUniform2f(const std::string& name, const Vec2& vector)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform2f(GetUniformLocation(name), vector);
        }

        void GLShader::SetUniform3f(const std::string& name, const Vec3& vector)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform3f(GetUniformLocation(name), vector);
        }

        void GLShader::SetUniform4f(const std::string& name, const Vec4& vector)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniform4f(GetUniformLocation(name), vector);
        }

        void GLShader::SetUniformMat4(const std::string& name, const Mat4& matrix)
        {
            LUMOS_PROFILE_FUNCTION();
            SetUniformMat4(GetUniformLocation(name), matrix);
        }

        void GLShader::SetUniform1f(uint32_t location, float value)
        {
            GLCall(glUniform1f(location, value));
        }

        void GLShader::SetUniform1fv(uint32_t location, float* value, int32_t count)
        {
            GLCall(glUniform1fv(location, count, value));
        }

        void GLShader::SetUniform1i(uint32_t location, int32_t value)
        {
            GLCall(glUniform1i(location, value));
        }

        void GLShader::SetUniform1ui(uint32_t location, uint32_t value)
        {
            GLCall(glUniform1ui(location, value));
        }

        void GLShader::SetUniform1iv(uint32_t location, int32_t* value, int32_t count)
        {
            GLCall(glUniform1iv(location, count, value));
        }

        void GLShader::SetUniform2f(uint32_t location, const Vec2& vector)
        {
            GLCall(glUniform2f(location, vector.x, vector.y));
        }

        void GLShader::SetUniform3f(uint32_t location, const Vec3& vector)
        {
            GLCall(glUniform3f(location, vector.x, vector.y, vector.z));
        }

        void GLShader::SetUniform4f(uint32_t location, const Vec4& vector)
        {
            GLCall(glUniform4f(location, vector.x, vector.y, vector.z, vector.w));
        }

        void GLShader::SetUniformMat3(uint32_t location, const Mat3& matrix)
        {
            GLCall(glUniformMatrix3fv(location, 1, GL_FALSE /*GLTRUE*/, Maths::ValuePtr(matrix))); // &matrix.values[0]));
        }

        void GLShader::SetUniformMat4(uint32_t location, const Mat4& matrix)
        {
            GLCall(glUniformMatrix4fv(location, 1, GL_FALSE /*GLTRUE*/, Maths::ValuePtr(matrix)));
        }

        void GLShader::SetUniformMat4Array(uint32_t location, uint32_t count, const Mat4& matrix)
        {
            GLCall(glUniformMatrix4fv(location, count, GL_FALSE /*GLTRUE*/, Maths::ValuePtr(matrix)));
        }

        void GLShader::LoadFromData(const uint32_t* data, uint32_t size, ShaderType type, std::map<ShaderType, std::string>& sources)
        {
            spirv_cross::CompilerGLSL* glsl = new spirv_cross::CompilerGLSL(data, size_t(size / sizeof(unsigned int)));

            // The SPIR-V is now parsed, and we can perform reflection on it.
            spirv_cross::ShaderResources resources = glsl->get_shader_resources();

            if(type == ShaderType::VERTEX)
            {
                uint32_t stride = 0;
                for(const spirv_cross::Resource& resource : resources.stage_inputs)
                {
                    const spirv_cross::SPIRType& InputType = glsl->get_type(resource.type_id);
                    // Switch to GL layout
                    PushTypeToBuffer(InputType, m_Layout);
                    stride += GetStrideFromOpenGLFormat(0); // InputType.width * InputType.vecsize / 8;
                }
            }

            // Get all sampled images in the shader.
            for(auto& resource : resources.sampled_images)
            {
                uint32_t set     = glsl->get_decoration(resource.id, spv::DecorationDescriptorSet);
                uint32_t binding = glsl->get_decoration(resource.id, spv::DecorationBinding);

                // Modify the decoration to prepare it for GLSL.
                // glsl->unset_decoration(resource.id, spv::DecorationDescriptorSet);

                // Some arbitrary remapping if we want.
                // glsl->set_decoration(resource.id, spv::DecorationBinding, set * 16 + binding);

                auto& descriptorInfo  = m_DescriptorInfos[set];
                auto& descriptor      = descriptorInfo.descriptors.EmplaceBack();
                descriptor.binding    = binding;
                descriptor.name       = resource.name;
                descriptor.shaderType = type;
                descriptor.type       = Graphics::DescriptorType::IMAGE_SAMPLER;
            }

            //                for(auto const& image : resources.separate_images)
            //                {
            //                    auto set { glsl->get_decoration(image.id, spv::Decoration::DecorationDescriptorSet) };
            //                    glsl->set_decoration(image.id, spv::Decoration::DecorationDescriptorSet, DESCRIPTOR_TABLE_INITIAL_SPACE + 2 * set);
            //                }
            //                for(auto const& input : resources.subpass_inputs)
            //                {
            //                    auto set { glsl->get_decoration(input.id, spv::Decoration::DecorationDescriptorSet) };
            //                    glsl->set_decoration(input.id, spv::Decoration::DecorationDescriptorSet, DESCRIPTOR_TABLE_INITIAL_SPACE + 2 * set);
            //                }
            for(auto const& uniform_buffer : resources.uniform_buffers)
            {
                auto set { glsl->get_decoration(uniform_buffer.id, spv::Decoration::DecorationDescriptorSet) };
                glsl->set_decoration(uniform_buffer.id, spv::Decoration::DecorationDescriptorSet, DESCRIPTOR_TABLE_INITIAL_SPACE + 2 * set);

                uint32_t binding = glsl->get_decoration(uniform_buffer.id, spv::DecorationBinding);
                auto& bufferType = glsl->get_type(uniform_buffer.type_id);

                auto bufferSize = glsl->get_declared_struct_size(bufferType);
                int memberCount = (int)bufferType.member_types.size();

                auto& descriptorInfo  = m_DescriptorInfos[set];
                auto& descriptor      = descriptorInfo.descriptors.EmplaceBack();
                descriptor.binding    = binding;
                descriptor.size       = (uint32_t)bufferSize;
                descriptor.name       = uniform_buffer.name;
                descriptor.offset     = 0;
                descriptor.shaderType = type;
                descriptor.type       = Graphics::DescriptorType::UNIFORM_BUFFER;
                descriptor.buffer     = nullptr;

                for(int i = 0; i < memberCount; i++)
                {
                    auto type              = glsl->get_type(bufferType.member_types[i]);
                    const auto& memberName = glsl->get_member_name(bufferType.self, i);
                    auto size              = glsl->get_declared_struct_member_size(bufferType, i);
                    auto offset            = glsl->type_struct_member_offset(bufferType, i);

                    std::string uniformName = uniform_buffer.name + "." + memberName;

                    auto& member    = descriptor.m_Members.EmplaceBack();
                    member.size     = (uint32_t)size;
                    member.offset   = offset;
                    member.type     = SPIRVTypeToLumosDataType(type);
                    member.fullName = uniformName;
                    member.name     = memberName;
                }
            }
            //                for(auto const& storage_buffer : resources.storage_buffers)
            //                {
            //                    auto set { glsl->get_decoration(storage_buffer.id, spv::Decoration::DecorationDescriptorSet) };
            //                    glsl->set_decoration(storage_buffer.id, spv::Decoration::DecorationDescriptorSet, DESCRIPTOR_TABLE_INITIAL_SPACE + 2 * set);
            //                }
            //                for(auto const& storage_image : resources.storage_images)
            //                {
            //                    auto set { glsl->get_decoration(storage_image.id, spv::Decoration::DecorationDescriptorSet) };
            //                    glsl->set_decoration(storage_image.id, spv::Decoration::DecorationDescriptorSet, DESCRIPTOR_TABLE_INITIAL_SPACE + 2 * set);
            //                }
            //
            //                for(auto const& sampler : resources.separate_samplers)
            //                {
            //                    auto set { glsl->get_decoration(sampler.id, spv::Decoration::DecorationDescriptorSet) };
            //                    glsl->set_decoration(sampler.id, spv::Decoration::DecorationDescriptorSet, DESCRIPTOR_TABLE_INITIAL_SPACE + 2 * set + 1);
            //                }

            for(auto& u : resources.push_constant_buffers)
            {
                uint32_t set     = glsl->get_decoration(u.id, spv::DecorationDescriptorSet);
                uint32_t binding = glsl->get_decoration(u.id, spv::DecorationBinding);
                glsl->set_decoration(u.id, spv::DecorationBinding, PUSHCONSTANT_BINDING);

                auto& pushConstantType = glsl->get_type(u.type_id);
                auto name              = glsl->get_name(u.id);

                auto ranges         = glsl->get_active_buffer_ranges(u.id);
                uint32_t rangeSizes = 0;
                for(auto& range : ranges)
                {
                    rangeSizes += uint32_t(range.range);
                }

                auto& bufferType = glsl->get_type(u.base_type_id);
                auto bufferSize  = glsl->get_declared_struct_size(bufferType);
                int memberCount  = (int)bufferType.member_types.size();

                m_PushConstants.PushBack({ /*rangeSizes*/ uint32_t(glsl->get_declared_struct_size(glsl->get_type(u.base_type_id))), type });
                m_PushConstants.Back().data = new uint8_t[size];
                m_PushConstants.Back().name = glsl->get_name(u.base_type_id);
                m_PushConstantsBuffers.push_back({ (GLUniformBuffer*)UniformBuffer::Create(size, nullptr), binding });
                m_PushConstantsBuffers.back().first->Init(size, nullptr);

                for(int i = 0; i < memberCount; i++)
                {
                    auto type              = glsl->get_type(bufferType.member_types[i]);
                    const auto& memberName = glsl->get_member_name(bufferType.self, i);
                    auto size              = glsl->get_declared_struct_member_size(bufferType, i);
                    auto offset            = glsl->type_struct_member_offset(bufferType, i);

                    std::string uniformName = u.name + "." + memberName;

                    auto& member    = m_PushConstants.Back().m_Members.EmplaceBack();
                    member.size     = (uint32_t)size;
                    member.offset   = offset;
                    member.type     = SPIRVTypeToLumosDataType(type);
                    member.fullName = uniformName;
                    member.name     = memberName;
                }
            }

            int imageCount[16]  = { 0 };
            int bufferCount[16] = { 0 };

            for(int i = 0; i < m_DescriptorInfos.size(); i++)
            {
                auto& descriptorInfo = m_DescriptorInfos[i];
                for(auto& descriptor : descriptorInfo.descriptors)
                {
                    if(descriptor.type == DescriptorType::IMAGE_SAMPLER)
                    {
                        imageCount[i]++;

                        if(i > 0)
                            descriptor.binding = descriptor.binding + imageCount[i - 1];
                    }
                    else if(descriptor.type == DescriptorType::UNIFORM_BUFFER)
                    {
                        bufferCount[i]++;

                        if(i > 0)
                            descriptor.binding = descriptor.binding + bufferCount[i - 1];
                    }
                }
            }

            spirv_cross::CompilerGLSL::Options options;
            options.version                              = 410;
            options.es                                   = false;
            options.vulkan_semantics                     = false;
            options.separate_shader_objects              = false;
            options.enable_420pack_extension             = false;
            options.emit_push_constant_as_uniform_buffer = true;
            options.vertex.fixup_clipspace               = true;
            glsl->set_common_options(options);

            // Compile to GLSL, ready to give to GL driver.
            std::string glslSource = glsl->compile();
            sources[type]          = glslSource;

            m_ShaderCompilers.push_back(glsl);
        }

        Shader* GLShader::CreateFuncGL(const std::string& filePath)
        {
            std::string physicalPath;
            Lumos::FileSystem::Get().ResolvePhysicalPath(filePath, physicalPath);
            GLShader* result = new GLShader(physicalPath);
            return result;
        }

        Shader* GLShader::CreateFromEmbeddedFuncGL(const uint32_t* vertData, uint32_t vertDataSize, const uint32_t* fragData, uint32_t fragDataSize)
        {
            return new GLShader(vertData, vertDataSize, fragData, fragDataSize);
        }

        Shader* GLShader::CreateCompFromEmbeddedFuncGL(const uint32_t* compData, uint32_t compDataSize)
        {
            return new GLShader(compData, compDataSize);
        }

        void GLShader::MakeDefault()
        {
            CreateFunc                 = CreateFuncGL;
            CreateFuncFromEmbedded     = CreateFromEmbeddedFuncGL;
            CreateCompFuncFromEmbedded = CreateCompFromEmbeddedFuncGL;
        }
    }
}
